package com.ctgu.elasticsearch.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScoreMode;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ctgu.elasticsearch.entity.HotelEntity;
import com.ctgu.elasticsearch.service.HotelService;
import com.ctgu.elasticsearch.mapper.HotelMapper;
import com.ctgu.elasticsearch.utils.ApiResult;
import com.ctgu.elasticsearch.utils.PageData;
import com.ctgu.elasticsearch.utils.Tools;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author Alex2
* @description 针对表【t_hotel】的数据库操作Service实现
* @createDate 2025-08-24 19:23:17
*/
@Log4j2
@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, HotelEntity>
    implements HotelService {

    private final ElasticsearchClient elasticsearchClient;

    public HotelServiceImpl(ElasticsearchClient elasticsearchClient) {
        this.elasticsearchClient = elasticsearchClient;
    }

    @Override
    public ApiResult insertHotel(String name, String address, Integer price, String brand, String city, BigDecimal longitude, BigDecimal latitude, Boolean isAD) throws IOException {
        HotelEntity hotel = lambdaQuery().eq(HotelEntity::getName, name).one();
        if (hotel != null) {
            log.error("该酒店已存在，插入失败");
            return ApiResult.error("该酒店已存在，插入失败");
        }
        HotelEntity hotelEntity = new HotelEntity(null, name, address, price, brand, city, longitude, latitude, isAD, null);
        save(hotelEntity);
        log.info("数据库插入成功");

        Map<String, Object> esDoc = Map.of(
                "id", hotelEntity.getId(),
                "name", name,
                "address", address,
                "price", price,
                "brand", brand,
                "city", city,
                "isAD", isAD,
                "location", Map.of("lat", latitude, "lon", longitude)
        );

        elasticsearchClient.index(i -> i
                .index("hotel")
                .id(String.valueOf(hotelEntity.getId()))
                .document(esDoc)
        );
        log.info("ES 插入成功，id={}", hotelEntity.getId());

        return ApiResult.error("数据库插入成功");
    }

    @Override
    public ApiResult keywordSearch(String keyword, Integer pageNum, Integer pageSize, String orderBy, String sortType) throws IOException {
        int from = (pageNum - 1) * pageSize;

        Set<String> textFields = Set.of("name", "address");
        String sortField = textFields.contains(orderBy) ? orderBy + ".keyword" : orderBy;
        SortOrder sortOrder = "asc".equalsIgnoreCase(sortType) ? SortOrder.Asc : SortOrder.Desc;

        var response = elasticsearchClient.search(s -> s
                        .index("hotel")
                        .from(from)
                        .size(pageSize)
                        .query(q -> q
                                .multiMatch(m -> m
                                        .fields("name", "address", "brand", "city")
                                        .query(keyword)
                                )
                        )
                        .sort(srt -> srt.field(f -> f.field(sortField).order(sortOrder))),
                HotelEntity.class
        );

        PageData<HotelEntity> pageData = Tools.buildPageData(response, pageSize);
        return ApiResult.success("搜索成功", pageData);
    }

    @Override
    public ApiResult filter(String brand, String address, String city, Integer maxPrice, Integer minPrice,
                            Integer pageNum, Integer pageSize, String orderBy, String sortType) throws IOException {

        int from = (pageNum - 1) * pageSize;

        // 优雅处理排序字段与顺序
        Set<String> textFields = Set.of("name", "address");
        String sortField = textFields.contains(orderBy) ? orderBy + ".keyword" : orderBy;
        SortOrder sortOrder = "asc".equalsIgnoreCase(sortType) ? SortOrder.Asc : SortOrder.Desc;

        // 构建 bool 查询
        var response = elasticsearchClient.search(s -> s
                        .index("hotel")
                        .from(from)
                        .size(pageSize)
                        .query(q -> q.functionScore(fs -> {
                            // 构建 bool 查询
                            fs.query(base -> base.bool(b -> {
                                if (brand != null && !brand.isEmpty()) {
                                    b.must(m -> m.term(t -> t.field("brand").value(brand)));
                                }
                                if (city != null && !city.isEmpty()) {
                                    b.must(m -> m.term(t -> t.field("city").value(city)));
                                }
                                if (address != null && !address.isEmpty()) {
                                    b.must(m -> m.match(mt -> mt.field("address").query(address)));
                                }
                                if (minPrice != null && maxPrice != null) {
                                    b.must(m -> m.range(r -> r
                                            .field("price")
                                            .gte(JsonData.of(minPrice))
                                            .lte(JsonData.of(maxPrice))
                                    ));
                                }
                                return b;
                            }));

                            // 提升 isAD=true 的权重
                            fs.functions(fn -> fn
                                    .filter(f -> f.term(t -> t.field("isAD").value(true)))
                                    .weight(5.0)
                            );

                            // 算分模式
                            fs.scoreMode(FunctionScoreMode.Multiply);
                            return fs;
                        }))
                        // 排序
                        .sort(srt -> srt.field(f -> f.field("_score").order(SortOrder.Desc)))
                        .sort(srt -> srt.field(f -> f.field(sortField).order(sortOrder))),
                HotelEntity.class
        );

        PageData<HotelEntity> pageData = Tools.buildPageData(response, pageSize);
        return ApiResult.success("过滤查询成功", pageData);
    }

    @Override
    public ApiResult orderByDistance(BigDecimal longitude, BigDecimal latitude, Integer pageNum, Integer pageSize, String orderBy, String sortType) throws IOException {
        int from = (pageNum - 1) * pageSize;

        // 处理排序字段
        Set<String> textFields = Set.of("name", "address");
        String sortField = textFields.contains(orderBy) ? orderBy + ".keyword" : orderBy;
        SortOrder sortOrder = "asc".equalsIgnoreCase(sortType) ? SortOrder.Asc : SortOrder.Desc;

        // 执行搜索
        var response = elasticsearchClient.search(s -> s
                        .index("hotel")
                        .from(from)
                        .size(pageSize)
                        // 按距离排序
                        // TODO: 地址有问题，应该返回距离，然后按距离排序
//                        .sort(st -> st.geoDistance(g -> g
//                                .field("location")
//                                .location(GeoLocation.of(b -> b.latlon(latitude.doubleValue(), longitude.doubleValue())))
//                                .order(SortOrder.Asc)
//                                .unit(DistanceUnit.Kilometers)
//                        ))
                        // 次级排序字段
                        .sort(st -> st.field(f -> f
                                .field(sortField)
                                .order(sortOrder)
                        )),
                HotelEntity.class
        );

        PageData<HotelEntity> pageData = Tools.buildPageData(response, pageSize);
        return ApiResult.success("按距离排序查询成功", pageData);
    }

    @Override
    public ApiResult aggregateSearch(String brand, String city, Integer pageNum, Integer pageSize,
                                     String orderBy, String sortType) throws IOException {
        int from = (pageNum - 1) * pageSize;
        SortOrder sortOrder = "asc".equalsIgnoreCase(sortType) ? SortOrder.Asc : SortOrder.Desc;

        // 优雅处理排序字段，如果是 text 字段需加 .keyword
        Set<String> textFields = Set.of("name", "address");
        String sortField = (orderBy != null && textFields.contains(orderBy)) ? orderBy + ".keyword" : orderBy;

        // 查询 + 聚合
        var response = elasticsearchClient.search(s -> {
            var builder = s.index("hotel")
                    .from(from)
                    .size(pageSize)
                    .query(q -> q.bool(b -> {
                        if (brand != null && !brand.isEmpty()) {
                            b.must(m -> m.term(t -> t.field("brand").value(brand)));
                        }
                        if (city != null && !city.isEmpty()) {
                            b.must(m -> m.term(t -> t.field("city").value(city)));
                        }
                        return b;
                    }))
                    // 聚合：直接用 brand 和 city
                    .aggregations("brandAgg", a -> a.terms(t -> t.field("brand")))
                    .aggregations("cityAgg", a -> a.terms(t -> t.field("city")));

            // 排序
            if (sortField != null && !sortField.isEmpty()) {
                builder.sort(srt -> srt.field(f -> f.field(sortField).order(sortOrder)));
            }

            return builder;
        }, HotelEntity.class);

        // 解析文档结果
        PageData<HotelEntity> pageData = Tools.buildPageData(response, pageSize);

        // 解析聚合结果
        Map<String, Object> aggsResult = new HashMap<>();
        var brandAgg = response.aggregations().get("brandAgg").sterms().buckets().array();
        var cityAgg = response.aggregations().get("cityAgg").sterms().buckets().array();

        aggsResult.put("brandAgg", brandAgg.stream()
                .map(b -> Map.of("key", b.key().stringValue(), "count", b.docCount()))
                .collect(Collectors.toList()));

        aggsResult.put("cityAgg", cityAgg.stream()
                .map(b -> Map.of("key", b.key().stringValue(), "count", b.docCount()))
                .collect(Collectors.toList()));

        // 返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("pageData", pageData);
        result.put("aggregations", aggsResult);

        return ApiResult.success("聚合查询成功", result);
    }

    public ApiResult pinyinSearch(String name) throws IOException {
        SearchResponse<HotelEntity> response = elasticsearchClient.search(s -> s
                        .index("hotel") // 索引名
                        .query(q -> q
                                .matchPhrasePrefix(m -> m
                                        .field("name")
                                        .query(name.toLowerCase())
                                )
                        )
                        .size(10), // 返回前 10 条
                HotelEntity.class
        );

        List<String> result = response.hits().hits().stream()
                .map(Hit::source)
                .filter(Objects::nonNull)
                .map(HotelEntity::getName)
                .collect(Collectors.toList());

        return ApiResult.success("自动补全查询成功", result);
    }
}